﻿using System;
using System.Collections.Generic;
using System.Text;
using lxwmvc;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;
using System.IO;
using System.Web;
using System.Xml;
using System.Security.Cryptography;

namespace lxwmvc
{
    /// <summary>
    /// 微信红包发送功能
    /// </summary>
    public class WeiXin_HongBao
    {
        /// <summary>
        /// 红包初始化功能
        /// </summary>
        /// <param name="appid"></param>
        /// <param name="mch_id"></param>
        /// <param name="zhifukey"></param>
        public WeiXin_HongBao(string appid, string mch_id, string zhifukey)
        {
            this.appid = appid;
            this.mch_id = mch_id;
            this.zhifukey = zhifukey;
        }
        //#region 云图点金
        ///// <summary>
        /////云图点金  商户号
        ///// </summary>
        //private const int dj_mch_id = 1239440002;
        ///// <summary>
        ///// 云图点金 支付密钥
        ///// </summary>
        //internal static string dj_zhifukey = "934b535800b1cba8f96a5d72f72f1611";
        //#endregion

        /// <summary>
        /// 微信 公众号ID
        /// </summary>
        //private static string appid = pwd.AppID;



        /// <summary>
        /// 发送红包给 指定帐号 
        /// </summary>
        /// <param name="openid">接受红包的用户openid</param>
        /// <param name="shuoming">红包说明 (祝福语)</param>
        /// <param name="jine">金额 (单位元)</param>
        /// <param name="dingdanhao"></param>
        /// <returns></returns>
        public WxPayData SendHongBao(string openid, string shuoming, double jine, string dingdanhao)
        {
            //金额
            int jineint = (jine * 100).toint32();
            
            Version version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
            string xiangmu = "V" + version.Major + "." + version.Minor;//syshelper.site_name() + 
            //拼凑xml

            WxPayData data = new WxPayData(zhifukey);
            data.SetValue("mch_billno", dingdanhao);//订单号

            string biaoti = "云图点金";

            data.SetValue("mch_id", mch_id);
            data.SetValue("wxappid", appid);
            data.SetValue("nick_name", xiangmu);//没有用


            data.SetValue("send_name", biaoti);//发送者名称
            data.SetValue("re_openid", openid);
            data.SetValue("total_amount", jineint);//金额 分
            data.SetValue("min_value", jineint);
            data.SetValue("max_value", jineint);
            data.SetValue("total_num", "1");
            data.SetValue("remark", "云图点金");//没有用

            data.SetValue("wishing", "感想您参加此次活动");//感谢您参加猜灯谜活动，祝您元宵节快乐！
            data.SetValue("client_ip", requesthelper.clientip());//syshelper.getIp(pb)
            data.SetValue("act_name", shuoming);////没有用猜越多得越多，快来抢！
            data.SetValue("logo_imgurl", "https://wx.gtimg.com/mch/img/ico-logo.png");//没有用
            data.SetValue("share_content", "感想您参加此次活动");//快来参加猜灯谜活动//没有用
            data.SetValue("share_url", "http://www.qq.com");
            data.SetValue("share_imgurl", "https://wx.gtimg.com/mch/img/ico-logo.png");

            data.SetValue("nonce_str", data.createNonceStr());
            data.SetValue("sign", data.MakeSign());

            string faxml = data.ToXml();//压制的X/ML

            string jiexml = Post(faxml, "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack");
            if (jiexml.ToUpper().IndexOf("SUCCESS") < 0)
            {
                dataloger.fatal("发送红包失败:" + jiexml + "--------------------------------------" + faxml);
                throw new Exception("发送红包失败:" + jiexml);
            }

            return data;
        }

        bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            //直接确认，否则打不开    
            return true;
        }

        /// <summary>
        /// 发送给微信进行交互
        /// </summary>
        /// <param name="xml">xml内容</param>
        /// <param name="url">url地址</param>
        /// <param name="isUseCert">使用证书</param>
        /// <param name="timeout">超时秒</param>
        /// <returns></returns>
        string Post(string xml, string url, bool isUseCert = true, int timeout = 10)
        {
            System.GC.Collect();//垃圾回收，回收没有正常关闭的http连接

            string result = "";//返回结果

            HttpWebRequest request = null;
            HttpWebResponse response = null;
            Stream reqStream = null;

            try
            {
                //设置最大连接数
                ServicePointManager.DefaultConnectionLimit = 200;
                //设置https验证方式
                if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                {
                    ServicePointManager.ServerCertificateValidationCallback =
                            new RemoteCertificateValidationCallback(CheckValidationResult);
                }

                /***************************************************************
                * 下面设置HttpWebRequest的相关属性
                * ************************************************************/
                request = (HttpWebRequest)WebRequest.Create(url);

                request.Method = "POST";
                request.Timeout = timeout * 1000;

                //设置代理服务器
                //WebProxy proxy = new WebProxy();                          //定义一个网关对象
                //proxy.Address = new Uri(WxPayConfig.PROXY_URL);              //网关服务器端口:端口
                //request.Proxy = proxy;

                //设置POST的数据类型和长度
                request.ContentType = "text/xml";
                byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
                request.ContentLength = data.Length;

                //是否使用证书
                if (isUseCert)
                {
                    string path = HttpContext.Current.Request.PhysicalApplicationPath;
                    dataloger.info(path + "cert\\" + mch_id + ".p12");
                    //X509Certificate2 cert = new X509Certificate2(path + "cert\\" + mch_id + ".p12", mch_id.tostr());

                    X509Certificate2 cert = new X509Certificate2(path + "cert\\" + mch_id + ".p12", mch_id.tostr(),
    X509KeyStorageFlags.MachineKeySet);

                    request.ClientCertificates.Add(cert);
                }

                //往服务器写入数据
                reqStream = request.GetRequestStream();
                reqStream.Write(data, 0, data.Length);
                reqStream.Close();

                //获取服务端返回
                response = (HttpWebResponse)request.GetResponse();

                //获取服务端返回数据
                using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                {
                    result = sr.ReadToEnd().Trim();
                }
            }
            catch (Exception e)
            {
                dataloger.fatal("请求证书错误" + e,e);
            }
            finally
            {
                //关闭连接和流
                if (response != null)
                {
                    response.Close();
                }
                if (request != null)
                {
                    request.Abort();
                }
            }
            return result;
        }


        string zhifukey { get; set; }
        string mch_id { get; set; }
        string appid { get; set; }
    }


    /// <summary>
    /// 微信支付协议接口数据类，所有的API接口通信都依赖这个数据结构，
    /// 在调用接口之前先填充各个字段的值，然后进行接口通信，
    /// 这样设计的好处是可扩展性强，用户可随意对协议进行更改而不用重新设计数据结构，
    /// 还可以随意组合出不同的协议数据包，不用为每个协议设计一个数据包结构
    /// </summary>
    public class WxPayData
    {
        public WxPayData(string zhifukey)
        {
            this.zhifukey = zhifukey;
        }

        //采用排序的Dictionary的好处是方便对数据包进行签名，不用再签名之前再做一次排序
        private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();

        /**
        * 设置某个字段的值
        * @param key 字段名
         * @param value 字段值
        */
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }

        /**
        * 根据字段名获取某个字段的值
        * @param key 字段名
         * @return key对应的字段值
        */
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }

        /**
         * 判断某个字段是否已设置
         * @param key 字段名
         * @return 若字段key已被设置，则返回true，否则返回false
         */
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }

        /**
        * @将Dictionary转成xml
        * @return 经转换得到的xml串
        * @throws WxPayException
        **/
        public string ToXml()
        {
            //数据为空时不能转化为xml格式
            if (0 == m_values.Count)
            {
                throw new Exception("WxPayData数据为空!");
            }

            string xml = "<xml>";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                //字段值不能为null，会影响后续流程
                if (pair.Value == null)
                {
                    throw new Exception("WxPayData内部含有值为null的字段!");
                }

                if (pair.Value.GetType() == typeof(int))
                {
                    xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
                }
                else if (pair.Value.GetType() == typeof(string))
                {
                    xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
                }
                else//除了string和int类型不能含有其他数据类型
                {
                    throw new Exception("WxPayData字段数据类型错误!");
                }
            }
            xml += "</xml>";
            return xml;
        }

        /**
        * @将xml转为WxPayData对象并返回对象内部的数据
        * @param string 待转换的xml串
        * @return 经转换得到的Dictionary
        * @throws WxPayException
        */
        public SortedDictionary<string, object> FromXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                throw new Exception("将空的xml串转换为WxPayData不合法!");
            }

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
            XmlNodeList nodes = xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe = (XmlElement)xn;
                m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }
            try
            {
                CheckSign();//验证签名,不通过会抛异常
            }
            catch (Exception ex)
            {
                dataloger.fatal("",ex);
            }

            return m_values;
        }

        /**
        * @Dictionary格式转化成url参数格式
        * @ return url格式串, 该串不包含sign字段值
        */
        public string ToUrl()
        {
            string buff = "";

            foreach (KeyValuePair<string, object> pair in m_values)
            {
                if (pair.Value == null)
                {
                    throw new Exception("WxPayData内部含有值为null的字段!");
                }

                if (pair.Key != "sign" && pair.Value.ToString() != "")
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }



        /**
        * @生成签名，详见签名生成算法
        * @return 签名, sign字段不参加签名
        */
        public string MakeSign()
        {
            //转url格式
            string str = ToUrl();
            //在string后加入API KEY
            str += "&key=" + zhifukey;//支付密钥934b535800b1cba8f96a5d72f72f1611
            //MD5加密
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
                sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }

        /**
        * 
        * 检测签名是否正确
        * 正确返回true，错误抛异常
        */
        public bool CheckSign()
        {
            //如果没有设置签名，则跳过检测
            if (!IsSet("sign"))
            {
                return true;
            }
            //如果设置了签名但是签名为空，则抛异常
            else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
            {
                throw new Exception("WxPayData签名存在但不合法!");
            }

            //获取接收到的签名
            string return_sign = GetValue("sign").ToString();

            //在本地计算新的签名
            string cal_sign = MakeSign();

            if (cal_sign == return_sign)
            {
                return true;
            }

            throw new Exception("WxPayData签名验证错误!");
        }

        /**
        * @获取Dictionary
        */
        public SortedDictionary<string, object> GetValues()
        {
            return m_values;
        }

        /// <summary>
        /// 生成随机数
        /// </summary>
        /// <returns></returns>
        public string createNonceStr()
        {
            int length = 32;
            string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            string str = "";
            Random rad = new Random();
            for (int i = 0; i < length; i++)
            {
                str += chars.Substring(rad.Next(0, chars.Length - 1), 1);
            }
            return str;
        }

        string zhifukey { get; set; }
    }
}
